package com.treasure.mall.biz.service.core.marketing.impl;

import com.treasure.mall.biz.base.*;
import com.treasure.mall.biz.config.redis.RedisConstant;
import com.treasure.mall.biz.dto.BaseSaveDTO;
import com.treasure.mall.biz.dto.BaseSearchDTO;
import com.treasure.mall.biz.dto.condition.RedPacketCondition;
import com.treasure.mall.biz.dto.marketing.RedPacketSaveDTO;
import com.treasure.mall.biz.dto.user.ModifyBalanceDTO;
import com.treasure.mall.biz.entity.embeddable.MerchantData_;
import com.treasure.mall.biz.entity.live.LiveRoomPO;
import com.treasure.mall.biz.entity.marketing.RedPacketGrabLogPO;
import com.treasure.mall.biz.entity.marketing.RedPacketPO;
import com.treasure.mall.biz.entity.marketing.RedPacketPO_;
import com.treasure.mall.biz.entity.user.MerchantPO;
import com.treasure.mall.biz.entity.user.MerchantPO_;
import com.treasure.mall.biz.entity.user.UserPO;
import com.treasure.mall.biz.entity.user.UserPO_;
import com.treasure.mall.biz.remote.live.TencentIMService;
import com.treasure.mall.biz.remote.live.dto.RedPacketMessageDTO;
import com.treasure.mall.biz.remote.live.enums.MessageType;
import com.treasure.mall.biz.repository.marketing.RedPacketGrabLogRepository;
import com.treasure.mall.biz.repository.marketing.RedPacketRepository;
import com.treasure.mall.biz.service.common.ScheduledFutures;
import com.treasure.mall.biz.service.core.live.LiveRoomService;
import com.treasure.mall.biz.service.core.marketing.RedPacketService;
import com.treasure.mall.biz.service.core.user.MerchantService;
import com.treasure.mall.biz.service.core.user.UserService;
import com.treasure.mall.zbq.common.Jsr310Utils;
import com.treasure.mall.zbq.common.MathUtils;
import com.treasure.mall.zbq.common.RandomUtils;
import com.treasure.mall.zbq.common.base.ApiResult;
import com.treasure.mall.zbq.common.exception.ApiException;
import com.treasure.mall.zbq.common.ienum.base.CodeDescEnumHelper;
import com.treasure.mall.zbq.common.ienum.log.AdvanceLogType;
import com.treasure.mall.zbq.common.ienum.marketing.RedPacketEnums;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;

/**
 * @author allan
 * @version 1.0.0
 * @date 2020/8/1
 */
@Service
public class RedPacketServiceImpl extends AbstractSearchableCrudService<RedPacketPO, Long> implements RedPacketService {
    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;
    @Autowired
    private RedisLocker redisLocker;
    @Autowired
    private RedPacketGrabLogRepository grabLogRepository;
    @Autowired
    private MerchantService merchantService;
    @Autowired
    private UserService userService;
    @Autowired
    private TencentIMService imService;
    @Autowired
    private LiveRoomService liveRoomService;
    private RedPacketRepository redPacketRepository;

    @Autowired
    protected RedPacketServiceImpl(RedPacketRepository repository) {
        super(repository);
        this.redPacketRepository = repository;
    }

    @Override
    protected SpecificationAndSort<RedPacketPO> specificationAndSort(BaseSearchDTO searchDTO) {
        RedPacketCondition condition = (RedPacketCondition) searchDTO;

        Specification<RedPacketPO> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            UserDetails userDetails = AuthHelper.getPrincipal();
            if (AuthHelper.isMerchant(userDetails)) {
                predicates.add(cb.equal(root.get(RedPacketPO_.merchantPO).get(MerchantPO_.merchantId), userDetails.getId()));
            }
            if (!StringUtils.isEmpty(condition.getTitle())) {
                predicates.add(cb.like(root.get(RedPacketPO_.title), likeAll(condition.getTitle())));
            }
            if (condition.getRedStatus() != null) {
                predicates.add(cb.equal(root.get(RedPacketPO_.status), condition.getRedStatus()));
            }

            if (!StringUtils.isEmpty(condition.getMerchantKey())) {
                predicates.add(cb.or(
                        cb.equal(root.get(RedPacketPO_.merchantPO).get(MerchantPO_.user).get(UserPO_.nickName), likeAll(condition.getMerchantKey())),
                        cb.equal(root.get(RedPacketPO_.merchantPO).get(MerchantPO_.user).get(UserPO_.mobile), likeAll(condition.getMerchantKey())),
                        cb.equal(root.get(RedPacketPO_.merchantPO).get(MerchantPO_.data).get(MerchantData_.title), likeAll(condition.getMerchantKey()))
                ));
            }

            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };

        Sort sort = new Sort(Sort.Direction.DESC, "createTime");

        return new SpecificationAndSort<>(specification, sort);
    }

    @Override
    @Transactional
    public RedPacketPO save(BaseSaveDTO saveDTO) {
        RedPacketSaveDTO dto = (RedPacketSaveDTO) saveDTO;

        //先判断一下够不够分
        if (dto.getAmount().doubleValue() / dto.getNum() < 0.01) {
            throw new ApiException(ApiResult.error("红包金额不够分"));
        }

        RedPacketPO po = new RedPacketPO();
        po.setStatus(RedPacketEnums.Status.TO_GRAB);
        MerchantPO merchantPO = merchantService.findById(dto.getMerchantId());
        if (MathUtils.greater(dto.getAmount(), merchantPO.getUser().getBalance())) {
            throw new ApiException(ApiResult.error("当前账户余额不足"));
        }
        po.setMerchantPO(merchantPO);
        po.setTitle(dto.getTitle());
        //要扣除余额
        po.setAmount(dto.getAmount());
        po.setNum(dto.getNum());
        po.setType(CodeDescEnumHelper.getEnumTypeByCode(RedPacketEnums.Type.class, dto.getType()));
        po.setStartTime(Jsr310Utils.DateTime.of(dto.getStartTime()));

        po = save(po);

        //扣除商家余额
        ModifyBalanceDTO modifyBalanceDTO = new ModifyBalanceDTO();
        modifyBalanceDTO.setAmount(po.getAmount().negate());
        modifyBalanceDTO.setRemark("创建红包扣除");
        modifyBalanceDTO.setId(merchantPO.getUser().getUserId());
        modifyBalanceDTO.setLogType(AdvanceLogType.LOG_TYPE13);
        userService.modifyBalance(modifyBalanceDTO);

        if (po.getStartTime().isBefore(LocalDateTime.now())) {
            start(po.getId());
        } else {
            //加入schedule
            ScheduledFuture future = ScheduledFutures.RED_PACKET_START.get(po.getId());
            if (future != null) {
                future.cancel(false);
            }
            final Long id = po.getId();
            ScheduledFuture newFuture = taskScheduler.schedule(() -> start(id), Jsr310Utils.DateTime.toDate(po.getStartTime()));
            ScheduledFutures.RED_PACKET_START.put(id, newFuture);
        }


        return po;
    }

    @Override
    public RedPacketPO currentRedPacket(Long merchantId) {
        return redPacketRepository.findByMerchantPO_MerchantIdAndEnabledIsTrue(merchantId);
    }

    @Override
    public void start(Long id) {
        RedPacketPO po = findById(id);
//        if (redPacketRepository.existsByMerchantPO_MerchantIdAndEnabledIsTrue(po.getMerchantPO().getMerchantId())) {
//            throw new ApiException(ApiResult.error("当前已有已经启用的红包"));
//        }
        if (po.getStatus() != RedPacketEnums.Status.TO_GRAB) {
            throw new ApiException(ApiResult.error("红包已开抢"));
        }
        if (po.getStartTime().isAfter(LocalDateTime.now())) {
            throw new ApiException(ApiResult.error("红包未到开枪时间"));
        }

        po.setStatus(RedPacketEnums.Status.GRABBING);

        save(po);
    }

    @Override
    public void enabledToggle(Long id) {
        RedPacketPO po = findById(id);
        if (po.getStatus() == RedPacketEnums.Status.CANCEL) {
            throw new ApiException(ApiResult.error("该红包已回收"));
        }
        if (po.isEnabled()) {
            po.setEnabled(false);
        } else {
            if (redPacketRepository.existsByMerchantPO_MerchantIdAndEnabledIsTrue(po.getMerchantPO().getMerchantId())) {
                throw new ApiException(ApiResult.error("当前已有上架红包"));
            }
            //上架
            po.setEnabled(true);
        }
        save(po);

        //当前直播间
        LiveRoomPO liveRoomPO = liveRoomService.findByMerchant(po.getMerchantPO().getMerchantId());
        imService.sendRedPacketMessage(
                RedPacketMessageDTO.builder()
                        .roomId(liveRoomPO.getId())
                        .messageType(po.isEnabled() ? MessageType.redPacketUp : MessageType.redPacketDown)
                        .build()
        );
    }

    @Override
    public RedPacketGrabLogPO grab(Long id, UserPO userPO) {
        RedPacketPO po;
        BigDecimal amount;
        while (true) {
            String lockKey = RedisConstant.LOCK_KEY_RED_PACKET + id;
            boolean locker = redisLocker.tryLock(lockKey, RedisConstant.LOCK_SERVICE_MARKETING, 10);
            if (locker) {
                try {
                    if (grabLogRepository.existsByRedPacketIdAndUserPO_UserId(id, userPO.getUserId())) {
                        throw new ApiException(ApiResult.error("已经抢过了"));
                    }
                    po = findById(id);
                    if (!po.isEnabled()) {
                        throw new ApiException(ApiResult.error("该红包未启用"));
                    }

                    if (po.getGrabNum() >= po.getNum()) {
                        throw new ApiException(ApiResult.error("红包已经抢完了"));
                    }
                    BigDecimal remainAmount = po.getAmount().subtract(po.getGrabAmount());
                    int remainNum = po.getNum() - po.getGrabNum();
                    if (remainNum == 1) {
                        //最后一次，把剩余的抢完
                        amount = remainAmount;
                        po.setStatus(RedPacketEnums.Status.COMPLETE);
                    } else {
                        if (po.getType() == RedPacketEnums.Type.NORMAL) {
                            //平均的
                            amount = po.getAmount().divide(BigDecimal.valueOf(po.getNum()), RoundingMode.HALF_UP);
                        } else {
                            //随机的
                            int remainAmountInt = remainAmount.multiply(BigDecimal.valueOf(100)).intValue();
                            int averageAmount = remainAmountInt / remainNum;
                            int randomAmount = RandomUtils.nextInt(1, averageAmount);
                            amount = BigDecimal.valueOf(randomAmount).divide(BigDecimal.valueOf(100), 2, RoundingMode.FLOOR);
                        }
                    }
                    po.setGrabNum(po.getGrabNum() + 1);
                    po.setGrabAmount(po.getGrabAmount().add(amount));
                    save(po);

                } finally {
                    redisLocker.release(lockKey, RedisConstant.LOCK_SERVICE_MARKETING);
                }
                break;
            }
        }

        //给用户加余额
        ModifyBalanceDTO modifyBalanceDTO = new ModifyBalanceDTO();
        modifyBalanceDTO.setId(userPO.getUserId());
        modifyBalanceDTO.setAmount(amount);
        modifyBalanceDTO.setLogType(AdvanceLogType.LOG_TYPE14);
        modifyBalanceDTO.setRemark("抢红包收入");
        userService.modifyBalance(modifyBalanceDTO);

        //日志
        RedPacketGrabLogPO logPO = new RedPacketGrabLogPO();
        logPO.setRedPacketId(po.getId());
        logPO.setAmount(amount);
        logPO.setUserPO(userPO);
        return grabLogRepository.save(logPO);
    }

    @Override
    public List<RedPacketGrabLogPO> getGrabLogs(Long redPacketId) {
        return grabLogRepository.findByRedPacketId(redPacketId);
    }

    @Override
    public boolean userGrabbed(Long redPacketId, Long userId) {
        return grabLogRepository.existsByRedPacketIdAndUserPO_UserId(redPacketId, userId);
    }

    @Override
    @Transactional
    public void recycle(Long redPacketId) {
        RedPacketPO po = findById(redPacketId);
        if (po.isEnabled()) {
            throw new ApiException(ApiResult.error("当前红包上架中，请先下架红包"));
        }

        po.setStatus(RedPacketEnums.Status.CANCEL);
        save(po);

        if (MathUtils.greater(po.remainAmount(), BigDecimal.ZERO)) {
            //退回红包
            ModifyBalanceDTO modifyBalanceDTO = new ModifyBalanceDTO();
            modifyBalanceDTO.setAmount(po.remainAmount());
            modifyBalanceDTO.setRemark("回收红包退回");
            modifyBalanceDTO.setId(po.getMerchantPO().getUser().getUserId());
            modifyBalanceDTO.setLogType(AdvanceLogType.LOG_TYPE15);
            userService.modifyBalance(modifyBalanceDTO);
        }
    }
}
